Skip to content

FE-512: Create FilterableListSubView with keyboard navigation and search#8532

Open
kube wants to merge 32 commits intographite-base/8532from
cf/fe-512-create-filterablelistsubview-component
Open

FE-512: Create FilterableListSubView with keyboard navigation and search#8532
kube wants to merge 32 commits intographite-base/8532from
cf/fe-512-create-filterablelistsubview-component

Conversation

@kube
Copy link
Collaborator

@kube kube commented Mar 12, 2026

Kapture.2026-03-13.at.11.01.04.mp4

🌟 What is the purpose of this PR?

Introduces the FilterableListSubView component and a global search panel for the Petrinaut editor sidebar. This provides a reusable, keyboard-navigable list pattern for all sidebar sections, along with fuzzy search across all entities.

🔗 Related links

🔍 What does this change?

FilterableListSubView

  • Add createFilterableListSubView factory for reusable sidebar list sections
  • Keyboard navigation (Arrow Up/Down), range selection (Shift+Click, Shift+Arrow), multi-select (Ctrl/Cmd+Click)
  • Row ellipsis menu with context actions
  • Focus management with visual focus indicator and blur reset

Search panel

  • Global search triggered via Ctrl/Cmd+F or Search button in sidebar headers
  • Fuzzy matching using fuzzysort with highlighted match results
  • Keyboard navigation between search input and results (ArrowDown enters list, ArrowUp on first item returns to input)
  • Escape closes search; empty query shows no results

VerticalSubViewsContainer improvements

  • Collapsible header border hidden when collapsed
  • renderTitle support for custom header content (used by search input)
  • Token-based header height and icon sizing

Other

  • Centralized entity icons
  • Keyboard focus highlighting on Menu items and submenu triggers
  • TopBar title input fills available space
  • Fix infinite render loop in EditorProvider by removing manual useCallback/useMemo (React Compiler handles memoization)

Pre-Merge Checklist 🚀

🚢 Has this modified a publishable library?

This PR:

  • modifies an npm-publishable library and I have added a changeset file(s)

📜 Does this require a change to the docs?

The changes in this PR:

  • are internal and do not require a docs change

🕸️ Does this require a change to the Turbo Graph?

The changes in this PR:

  • do not affect the execution graph

❓ How to test this?

  1. Checkout the branch and run yarn dev in libs/@hashintel/petrinaut
  2. Open the sidebar — verify lists are keyboard navigable (Arrow keys, Shift+Click range, Ctrl+Click toggle)
  3. Press Ctrl/Cmd+F — search panel appears with animated transition
  4. Type to fuzzy-search entities — matches are highlighted
  5. Use Arrow Down to navigate into results, Arrow Up on first result returns to input
  6. Press Escape to close search

@github-actions github-actions bot added area/libs Relates to first-party libraries/crates/packages (area) type/eng > frontend Owned by the @frontend team labels Mar 12, 2026
@vercel
Copy link

vercel bot commented Mar 12, 2026

The latest updates on your projects. Learn more about Vercel for GitHub.

Project Deployment Actions Updated (UTC)
hash Ready Ready Preview, Comment Mar 15, 2026 0:24am
petrinaut Ready Ready Preview Mar 15, 2026 0:24am
2 Skipped Deployments
Project Deployment Actions Updated (UTC)
hashdotdesign Ignored Ignored Preview Mar 15, 2026 0:24am
hashdotdesign-tokens Ignored Ignored Preview Mar 15, 2026 0:24am

Copy link
Collaborator Author

kube commented Mar 12, 2026

@cursor
Copy link

cursor bot commented Mar 13, 2026

PR Summary

Medium Risk
Introduces new sidebar selection/navigation behavior (keyboard handling, range/multi-select) and a new global search panel, which could regress focus/selection UX or shortcut handling despite being UI-scoped.

Overview
Adds a reusable createFilterableListSubView pattern for left-sidebar entity lists, including keyboard navigation, shift-range selection, ctrl/cmd multi-select, and per-row overflow menus for actions like delete.

Introduces a new global left-sidebar Search view (toggleable via header search buttons and Ctrl/Cmd+F) backed by fuzzysort, with highlighted fuzzy matches across nodes/types/equations/parameters and focus management via a shared searchInputRef in EditorContext.

Updates SubView/VerticalSubViewsContainer to support header icons, custom renderTitle for main headers, and alwaysShowHeaderAction, and applies various UI polish (menu highlighted states, tooltip icon variants, centralized entity-icons, minor layout/styling tweaks).

Written by Cursor Bugbot for commit 956fe86. This will update automatically on new commits. Configure here.

@augmentcode
Copy link

augmentcode bot commented Mar 13, 2026

🤖 Augment PR Summary

Summary: Refines Petrinaut’s vertical subview headers to better match updated UI behavior and adds reusable list subviews.

Changes:

  • Updated VerticalSubViewsContainer header styling: header actions/tooltip reveal on hover/focus and chevron visibility behavior.
  • Extended SubView to support optional header icons and an alwaysShowHeaderAction flag.
  • Introduced centralized entity icon exports (entity-icons.tsx) and applied them to PropertiesPanel headers.
  • Refactored left sidebar lists (nodes/types/parameters/differential equations) onto a new createFilterableListSubView factory with consistent row styling and per-row menus.
  • Adjusted menu/tooltip/section styling tokens for updated design-system alignment (highlight states, borders, radii, spacing).

Technical Notes: New filterable list subviews add keyboard navigation and multi-select range selection, and move destructive actions into an ellipsis menu using the shared Menu component.

🤖 Was this summary useful? React with 👍 or 👎

Copy link

@augmentcode augmentcode bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Review completed. 3 suggestions posted.

Fix All in Augment

Comment augment review to trigger a new review at any time.

<item.icon size={LIST_ITEM_ICON_SIZE} />
</span>
)}
<span className={listItemNameStyle}>
Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

renderItem() is wrapped in a <span>, but some callers (e.g. parameters) return a <div> which creates invalid HTML (div inside span) and can lead to layout/hydration warnings. Consider using a block wrapper or constraining renderItem to inline content.

Severity: medium

Fix This in Augment

🤖 Was this useful? React with 👍 or 👎, or 🚀 if it prevented an incident/outage.

Copy link
Collaborator Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Fixed — changed the <span> wrapper to <div> so block-level renderItem content doesn't create invalid HTML.

[items, getSelectionItem, setSelection],
);

const handleKeyDown = useCallback(
Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The listbox-level onKeyDown will also fire when focus is on nested controls (e.g. the row menu IconButton), so Enter/Space/arrow keys can interfere with opening/navigating menus. Consider ignoring key handling unless the event originated from the container itself.

Severity: medium

Fix This in Augment

🤖 Was this useful? React with 👍 or 👎, or 🚀 if it prevented an incident/outage.

Copy link
Collaborator Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Fixed — added event.target \!== event.currentTarget guard so the listbox-level onKeyDown ignores events bubbling from nested controls like menu buttons.

@kube kube force-pushed the graphite-base/8532 branch from 0b08c25 to 44fe283 Compare March 13, 2026 01:28
@kube kube force-pushed the cf/fe-512-create-filterablelistsubview-component branch from 39823b5 to ae532f6 Compare March 13, 2026 01:28
@kube kube changed the base branch from graphite-base/8532 to main March 13, 2026 01:28
@github-actions github-actions bot added the area/deps Relates to third-party dependencies (area) label Mar 13, 2026
@kube kube changed the title Show chevron on hover in VerticalSubViewsContainer Header FE-512: Create FilterableListSubView with keyboard navigation and search Mar 13, 2026
},
emptyMessage: "No global parameters yet",
renderHeaderAction: () => <ParametersHeaderAction />,
});
Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Simulation parameter value editing feature lost in refactor

Medium Severity

The refactor from a custom component to createFilterableListSubView removed the inline NumberInput that allowed changing parameter values during simulation mode. The old code used SimulationContext (parameterValues, setParameterValue) to render an editable input for each parameter when globalMode === "simulate". The new code only hides the delete menu item in simulation mode but provides no way to edit parameter values inline.

Fix in Cursor Fix in Web

Copy link
Collaborator Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Valid observation — the inline NumberInput for editing parameter values during simulation mode was removed when migrating to the createFilterableListSubView pattern. This will be addressed in a follow-up since it requires a per-item renderAction slot that doesn't fit the current generic list abstraction.

kube and others added 6 commits March 14, 2026 14:55
- Add isSearchOpen state and searchInputRef to EditorContext
- Create SearchPanel subview with input in header via renderTitle
- Add renderTitle to SubView type for custom main header content
- Swap between normal subviews and search panel with CSS transition
- Ctrl/Cmd+F opens search or focuses input if already open
- Escape closes search globally via keyboard shortcuts hook
- Wire Search button in FilterableListSubView header to open search

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
Search all sidebar items (nodes, types, equations, parameters) with
fuzzy matching via fuzzysort. Matched characters are highlighted in
results. Shows match count at top, all items when query is empty.

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
… call

ArrowDown from the search input moves focus to the first result. ArrowUp/Down
navigates through results, selecting each item. ArrowUp on the first result
returns focus to the input. Also fixes the fuzzysort highlight call to use the
instance method (result.highlight) instead of the non-existent static method.

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
Hide the Filter and Sort IconButtons from FilterHeaderAction (not yet
implemented). Remove all useCallback and useMemo wrappers from changed
files so the React Compiler can optimize the components. Also fix search
panel to show empty state when no query and prevent onFocus render loop.

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
… handler

Rename getMenuItems → useMenuItems so the linter recognizes these
callbacks as hooks (they call use() and useIsReadOnly() inside RowMenu's
render). Add onKeyDown to the row div to satisfy the
click-events-have-key-events a11y rule.

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
kube and others added 3 commits March 14, 2026 19:30
- Fix invalid HTML: change <span> to <div> for listItemNameStyle wrapper
  so block-level renderItem content (e.g. parameters) nests correctly
- Guard listbox onKeyDown to ignore events bubbling from nested controls
- Prevent Ctrl+F from intercepting inside Monaco/inputs by checking
  isInputFocused before handling the shortcut
- Restrict Escape-to-close-search to only fire when search input is focused
- Remove duplicate onKeyDown on search result rows (parent listbox handles
  navigation; rows now only handle Enter/Space for a11y)
- Add alwaysShowHeaderAction to multi-selection panel for consistency
- Remove commented-out CSS in tooltip.tsx
- Update stale doc comment (getMenuItems → useMenuItems)

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
The compiler flagged useMenuItems and useItems as dynamic hook props.
Fix by:
- Replacing useMenuItems callback with renderRowMenu component prop,
  so each consumer defines a proper React component (DiffEqRowMenu,
  ParameterRowMenu, TypeRowMenu) that calls hooks safely
- Moving useItems() call from FilterableListContent into the factory's
  Component, passing plain items data down instead of a hook prop
- Exporting RowMenu helper for consumers to render shared menu chrome

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
Adds overflow: hidden to editorRootStyle so portalled menus and
tooltips don't render outside the component boundary.

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
Copy link

@cursor cursor bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Cursor Bugbot has reviewed your changes and found 1 potential issue.

There are 2 total unresolved issues (including 1 from previous review).

Fix All in Cursor

Bugbot Autofix is OFF. To automatically fix reported issues with cloud agents, enable autofix in the Cursor dashboard.

const listRef = useRef<HTMLDivElement>(null);
const rowRefs = useRef<(HTMLDivElement | null)[]>([]);

const { searchInputRef } = use(EditorContext);
Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Duplicate use(EditorContext) call in SearchContent component

Low Severity

SearchContent calls use(EditorContext) twice — once at line 205 to destructure isSelected and selectItem, and again at line 212 to destructure searchInputRef. These could be a single use() call with a combined destructuring.

Fix in Cursor Fix in Web

Copy link
Collaborator Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Fixed — merged the two use(EditorContext) calls into a single destructuring.

Comment on lines +127 to +149
<div
className={contentLayerStyle({
active: !isSearchOpen,
direction: "forward",
})}
>
<VerticalSubViewsContainer
name="left-sidebar"
subViews={LEFT_SIDEBAR_SUBVIEWS}
/>
</div>
<div
className={contentLayerStyle({
active: isSearchOpen,
direction: "backward",
})}
>
<VerticalSubViewsContainer
name="left-sidebar-search"
subViews={searchSubViews}
/>
</div>
</div>
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Both content layers (main sidebar and search) are rendered simultaneously with only opacity and pointerEvents controlling visibility. The pointerEvents: 'none' prevents mouse interaction but does not prevent keyboard focus. Users can still Tab into invisible/inactive elements in the hidden layer, causing confusing navigation.

Fix: Add visibility: 'hidden' or conditionally render the inactive layer:

compoundVariants: [
  {
    active: false,
    css: { 
      transform: "translateX(-8px)",
      visibility: "hidden" // Prevents keyboard focus
    }
  }
]
Suggested change
<div
className={contentLayerStyle({
active: !isSearchOpen,
direction: "forward",
})}
>
<VerticalSubViewsContainer
name="left-sidebar"
subViews={LEFT_SIDEBAR_SUBVIEWS}
/>
</div>
<div
className={contentLayerStyle({
active: isSearchOpen,
direction: "backward",
})}
>
<VerticalSubViewsContainer
name="left-sidebar-search"
subViews={searchSubViews}
/>
</div>
</div>
{!isSearchOpen ? (
<div
className={contentLayerStyle({
active: true,
direction: "forward",
})}
>
<VerticalSubViewsContainer
name="left-sidebar"
subViews={LEFT_SIDEBAR_SUBVIEWS}
/>
</div>
) : (
<div
className={contentLayerStyle({
active: true,
direction: "backward",
})}
>
<VerticalSubViewsContainer
name="left-sidebar-search"
subViews={searchSubViews}
/>
</div>
)}
</div>

Spotted by Graphite

Fix in Graphite


Is this helpful? React 👍 or 👎 to let us know.

Copy link
Collaborator Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Fixed — added visibility: "hidden" to the inactive content layer so keyboard focus can't reach invisible elements.

- Merge two use(EditorContext) calls into one in SearchContent
- Add visibility: hidden to inactive sidebar content layer to prevent
  keyboard focus reaching invisible elements

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
@graphite-app
Copy link
Contributor

graphite-app bot commented Mar 15, 2026

Merge activity

  • Mar 15, 11:00 PM UTC: This pull request can not be added to the Graphite merge queue. Please try rebasing and resubmitting to merge when ready.
  • Mar 15, 11:00 PM UTC: Graphite disabled "merge when ready" on this PR due to: a merge conflict with the target branch; resolve the conflict and try again..

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

area/deps Relates to third-party dependencies (area) area/libs Relates to first-party libraries/crates/packages (area) type/eng > frontend Owned by the @frontend team

Development

Successfully merging this pull request may close these issues.

1 participant